# -*- coding:utf-8 -*-
# AudibleAlgos
# Vincent Foley-Bourgon - vfoley <at> gmail <dot> com

from pyo import *

import math
import random
import wx
from wx.lib.pubsub import Publisher as pub

import sorting
import generators

### Excellent! Les algos fonctionnent bien. La prochaine etape serait de travailler
### sur le timbre sonore et comment les algos le font jouer. Une classe audio serait
### bien, plutot que de creer les objets pyo directement dans la classe panel.
### Voi commentaires dans SortPanel...
### 19/20

ALGORITHMS = [
    ("Merge sort (fast)", sorting.MergeSort),
    ("Quick sort (fast)", sorting.QuickSort),
    ("Bubble sort (slow)", sorting.BubbleSort),
    ("Gnome sort (slow)", sorting.GnomeSort),
    ("Insertion sort (slow)", sorting.InsertionSort),
    ("Selection sort (slow)", sorting.SelectionSort),
]

NOTES_CHOICES = [
    ("Random", generators.AnyValue),
    ("A Minor Pentatonic", generators.AMinPenta),
] + \
[(x + " Major Scale", lambda: generators.MajorScale(x)) for x in "CDEFGAB"]

BEAT_CHOICES = ["Metro", "Beat"]

MIN_ELEMS = 10
MAX_ELEMS = 200
INITIAL_ELEMS = 50
INITIAL_SPEED = 85

server = Server().boot()
server.start()

class ControlPanel(wx.Panel):
    """
    This panel will contain the DisplayPanel where the sorting animation
    will take place, as well as the different GUI widgets to control the
    parameters of the animation (e.g.: sorting algorithms, shuffling, etc.)
    """
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)
        sizer = wx.BoxSizer(wx.VERTICAL)
        controlSizer = wx.BoxSizer(wx.HORIZONTAL)
        controlSizer2 = wx.BoxSizer(wx.HORIZONTAL)

        # GUI elements
        self.algoChoice = wx.Choice(self, -1, choices=[a[0] for a in ALGORITHMS])
        self.numElemLbl = wx.StaticText(self, -1, "Number of elements")
        self.numElemSpin = wx.SpinCtrl(self, -1, min=MIN_ELEMS, max=MAX_ELEMS, initial=INITIAL_ELEMS)
        self.speedLbl = wx.StaticText(self, -1, "Speed (ms)")
        self.speedSlider = wx.Slider(self, -1, minValue=5, maxValue=250, value=INITIAL_SPEED, style=wx.SL_LABELS)
        self.startBtn = wx.Button(self, -1, "Start")
        self.stopBtn = wx.Button(self, -1, "Stop")
        self.stopBtn.Enable(False)
        self.shuffleBtn = wx.Button(self, -1, "Shuffle")
        self.notesChoice = wx.Choice(self, -1, choices=[a[0] for a in NOTES_CHOICES])
        self.beatChoice = wx.Choice(self, -1, choices=BEAT_CHOICES)
        self.sortPanel = SortPanel(self)

        # Events binding
        self.algoChoice.Bind(wx.EVT_CHOICE, self.ChangeAlgo)
        self.numElemSpin.Bind(wx.EVT_SPINCTRL, self.ChangeNumElems)
        self.speedSlider.Bind(wx.EVT_SLIDER, self.ChangeSpeed)
        self.startBtn.Bind(wx.EVT_BUTTON, self.Start)
        self.stopBtn.Bind(wx.EVT_BUTTON, self.Stop)
        self.shuffleBtn.Bind(wx.EVT_BUTTON, self.Shuffle)
        self.notesChoice.Bind(wx.EVT_CHOICE, self.ChangeNotes)
        self.beatChoice.Bind(wx.EVT_CHOICE, self.ChangeBeat)

        self.SetSizer(sizer)
        controlSizer.Add(self.algoChoice, 0, wx.EXPAND)
        controlSizer.Add(self.numElemLbl, 0, wx.ALIGN_CENTER)
        controlSizer.Add(self.numElemSpin, 0, wx.EXPAND)
        controlSizer.Add(self.speedLbl, 0, wx.ALIGN_CENTER)
        controlSizer.Add(self.speedSlider, 1, wx.EXPAND)
        controlSizer.Add(self.startBtn, 0, wx.EXPAND)
        controlSizer.Add(self.stopBtn, 0, wx.EXPAND)
        controlSizer.Add(self.shuffleBtn, 0, wx.EXPAND)

        controlSizer2.Add(self.notesChoice, 0, wx.EXPAND)
        controlSizer2.Add(self.beatChoice, 0, wx.EXPAND)

        sizer.Add(self.sortPanel, 1, wx.EXPAND|wx.ALL, 0)
        sizer.Add(controlSizer, 0, wx.EXPAND|wx.ALL, 0)
        sizer.Add(controlSizer2, 0, wx.EXPAND|wx.ALL, 0)

        pub.subscribe(self.OnSortingChange, 'sorting')

        self.ChangeAlgo(None)
        self.ChangeSpeed(None)


    def OnSortingChange(self, message):
        self.startBtn.Enable(not message.data)
        self.algoChoice.Enable(not message.data)
        self.numElemSpin.Enable(not message.data)
        self.shuffleBtn.Enable(not message.data)
        self.notesChoice.Enable(not message.data)
        self.beatChoice.Enable(not message.data)
        self.stopBtn.Enable(message.data)

    def ChangeAlgo(self, evt):
        self.sortPanel.sorter = ALGORITHMS[self.algoChoice.GetSelection()][1]

    def ChangeNotes(self, evt):
        self.sortPanel.noteGenerator = NOTES_CHOICES[self.notesChoice.GetSelection()][1]
        self.ChangeNumElems(None)

    def ChangeBeat(self, evt):
        self.sortPanel.ChangeBeat(BEAT_CHOICES[self.beatChoice.GetSelection()])

    def ChangeSpeed(self, evt):
        self.sortPanel.SetSpeed(self.speedSlider.GetValue())

    def ChangeNumElems(self, evt):
        self.sortPanel.GenerateNElements(self.numElemSpin.GetValue())

    def Shuffle(self, evt):
        self.sortPanel.Shuffle()

    def Start(self, evt):
        self.sortPanel.Sort()

    def Stop(self, evt):
        self.sortPanel.Stop()


class SortPanel(wx.Panel):
    """
    Display the elements of a list with vertical bars.
    """
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)

        self.noteGenerator = NOTES_CHOICES[0][1]
        self.GenerateNElements(INITIAL_ELEMS)
        self.currents = set()

        self.tbl = LinTable([(0, 0), (2000, .8), (8000, .4), (8192, 0)])
        self.metro = Metro()
        self.metro_env = TrigEnv(self.metro, self.tbl, dur=self.metro.time)
        self.beat = Beat()
        self.beat_env = TrigEnv(self.beat, self.tbl, dur=self.beat.time)


        ### Le SineLoop devrait etre cree ici (pourrait etre plus excitant d'ailleurs!)
        ### Les barres devraient representer des notes midi et etre converties en Hz
        ### avec midiToHz avant d'appeler self.sine.setFreq.
        ### Pourrait aussi etre une combinaison d'objets audio:
        ### self.midiNote = Sig() self.sine.setFreq devient self.midiNote.setValue
        ### self.snap = Snap(self.midiNote, choice=[le choix des notes possibles], scale=1)
        ### self.sine = SineLoop(freq=self.snap, ...)

        self.notes = Sig([0, 0, 0, 0])
        self.sine = SineLoop(freq=self.notes, mul=self.metro_env)
        self.updater = TrigFunc(self.metro, self.UpdateAction)

        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.Bind(wx.EVT_SIZE, self.OnResize)


    def GenerateNElements(self, n):
        self.xs = [self.noteGenerator() for _ in xrange(n)]
        self.min = min(self.xs)
        self.max = max(self.xs)

        self.Refresh()

    def ChangeBeat(self, beat_name):
        if beat_name == "Metro":
            self.sine.mul = self.metro_env
            self.updater.setInput(self.metro, 0)
        elif beat_name == "Beat":
            self.sine.mul = self.beat_env
            self.updater.setInput(self.beat, 0)

    def Shuffle(self):
        random.shuffle(self.xs)
        self.Refresh()

    def SetSpeed(self, n):
        self.metro.setTime(n / 1000.0)
        self.metro_env.setDur(self.metro.time)
        self.beat.setTime(n / 1000.0)
        self.beat_env.setDur(self.beat.time)

    def OnResize(self, evt):
        self.Refresh()

    def OnPaint(self, evt):
        """
        Draw the bars on the panel.  Green by default, red if the
        element is being worked on (e.g. swapped, compared, etc.)
        """
        w, h = self.GetSize()
        dc = wx.AutoBufferedPaintDC(self)
        dc.SetBrush(wx.Brush("white"))
        dc.SetPen(wx.Pen("black"))
        dc.Clear()

        bar_width = w / len(self.xs)
        for i, x in enumerate(self.xs):
            height = int(h * (float(x) - self.min) / (self.max - self.min))
            if i in self.currents:
                dc.SetBrush(wx.Brush("#aa0000"))
            else:
                dc.SetBrush(wx.Brush("#004400"))
            dc.DrawRectangle(i*bar_width, h-height, bar_width, height)

    def UpdateAction(self):
        try:
            self.currents = next(self.action_iterator)
            self.notes.setValue([self.xs[i] for i in self.currents])
        except StopIteration:
            self.currents = set()
            self.Stop()
        self.Refresh()

    def Sort(self):
        self.action_iterator = self.sorter(self.xs)
        self.metro.play()
        self.beat.play()
        self.sine.out()
        pub.sendMessage("sorting", True)


    def Stop(self):
        self.metro.stop()
        self.beat.stop()
        self.sine.stop()
        self.currents = []
        self.Refresh()
        pub.sendMessage("sorting", False)



app = wx.App()
frame = wx.Frame(None, -1, size=(1024, 768), title="Audible sorting")
panel = ControlPanel(frame)
frame.Show(True)
app.MainLoop()
